using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Audio;

namespace TankWars.Weapons
{
    public class WeaponManager
    {
        public enum WeaponCategory {Ammo, Energy, Infinite};

        private Tank tank;

        private Dictionary<Type, Texture2D> dicShotIcons = new Dictionary<Type, Texture2D>();
        private Dictionary<Type, Texture2D> dicShotTextures = new Dictionary<Type, Texture2D>();

        private Dictionary<Type, string> dicShotSpriteNames = new Dictionary<Type, string>();
        private string strAnimationXMLFile = "";

        private Type tyPrimaryShotType;
        private Type tyDefaultPrimaryShotType = typeof(Missile);
        private Type tySecondaryShotType;
        private Type tyDefaultSecondaryShotType = typeof(Mine);

        private bool bPrimaryReadyToFire = true;
        private double dPrimaryLastTimeFired = 0;
        private double dPrimaryTimeBetweenFiring = 0;

        private bool bSecondaryReadyToFire = true;
        private double dSecondaryLastTimeFired = 0;
        private double dSecondaryTimeBetweenFiring = 0;

        private List<Shot> lstPrimaryShotsFired = new List<Shot>();
        private List<Shot> lstSecondaryShotsFired = new List<Shot>();

        AudioEngine AEngine;
        WaveBank tankWavs;
        SoundBank tankSounds;
        Cue primshot;
        Cue anim;

        private WeaponCategory PrimaryWeaponAmmoType = WeaponCategory.Infinite;
        private WeaponCategory SecondaryWeaponAmmoType = WeaponCategory.Infinite;
        private int nPrimaryAmmo = 0;
        private int nPrimaryEnergy = 0;
        private int nPrimaryChargeCostPerFire = 0;
        private int nSecondaryAmmo = 0;
        private int nSecondaryEngery = 0;
        private int nSecondaryChargeCostPerFire = 0;

        #region Properties
        public List<Shot> PrimaryShotsFired
        {
            get
            {
                return this.lstPrimaryShotsFired;
            }
        }

        public List<Shot> SecondaryShotsFired
        {
            get
            {
                return this.lstSecondaryShotsFired;
            }
        }

        /// <summary>
        /// Gets and sets the type of primary shot the tank fires.
        /// </summary>
        public Type PrimaryShotType
        {
            get
            {
                return this.tyPrimaryShotType;
            }
            set
            {
                //The the given type is a shot then it's ok, otherwise keep the current shot type
                if (value.BaseType == typeof(Shot))
                {
                    this.tyPrimaryShotType = value;
                }

                //Get the time between firing for the given shot
                double dTempTimeBetweenFiring = 0;
                System.Reflection.FieldInfo fieldInfo = this.tyPrimaryShotType.GetField("N_TIME_BETWEEN_FIRING");
                if (fieldInfo != null)
                {
                    dTempTimeBetweenFiring = (int)fieldInfo.GetValue(null);
                }

                this.dPrimaryTimeBetweenFiring = dTempTimeBetweenFiring;

                fieldInfo = this.tyPrimaryShotType.GetField("SHOT_CATEGORY");
                if (fieldInfo != null)
                {
                    this.PrimaryWeaponAmmoType = (WeaponCategory)fieldInfo.GetValue(null);
                }

                if (this.PrimaryWeaponAmmoType == WeaponCategory.Ammo)
                {
                    fieldInfo = this.tyPrimaryShotType.GetField("N_MAX_AMMO");
                    this.nPrimaryAmmo = (int)fieldInfo.GetValue(null);
                }
                else if (this.PrimaryWeaponAmmoType == WeaponCategory.Energy)
                {
                    fieldInfo = this.tyPrimaryShotType.GetField("N_CHARGE_COST_PER_FIRE");
                    this.nPrimaryChargeCostPerFire = (int)fieldInfo.GetValue(null);

                    this.nPrimaryEnergy = 100;
                }
                else
                {
                    this.nPrimaryAmmo = 0;
                    this.nPrimaryEnergy = 0;
                    this.nPrimaryChargeCostPerFire = 0;
                }   
            }
        }
        /// <summary>
        /// Gets and sets the type of secondary shot the tank fires.
        /// </summary>
        public Type SecondaryShotType
        {
            get
            {
                return this.tySecondaryShotType;
            }
            set
            {
                //The the given type is a shot then it's ok, otherwise keep the current shot type
                if (value.BaseType == typeof(Shot))
                {
                    this.tySecondaryShotType = value;
                }

                //Get the time between firing for the given shot
                double dTempTimeBetweenFiring = 0;
                System.Reflection.FieldInfo fieldInfo = this.tySecondaryShotType.GetField("N_TIME_BETWEEN_FIRING");
                if (fieldInfo != null)
                {
                    dTempTimeBetweenFiring = (int)fieldInfo.GetValue(null);
                }

                this.dSecondaryTimeBetweenFiring = dTempTimeBetweenFiring;

                fieldInfo = this.tySecondaryShotType.GetField("SHOT_CATEGORY");
                if (fieldInfo != null)
                {
                    this.SecondaryWeaponAmmoType = (WeaponCategory)fieldInfo.GetValue(null);
                }

                if (this.SecondaryWeaponAmmoType == WeaponCategory.Ammo)
                {
                    fieldInfo = this.tySecondaryShotType.GetField("N_MAX_AMMO");
                    this.nSecondaryAmmo = (int)fieldInfo.GetValue(null);
                }
                else if (this.SecondaryWeaponAmmoType == WeaponCategory.Energy)
                {
                    fieldInfo = this.tySecondaryShotType.GetField("N_CHARGE_COST_PER_FIRE");
                    this.nSecondaryChargeCostPerFire = (int)fieldInfo.GetValue(null);

                    this.nSecondaryEngery = 100;
                }
                else
                {
                    this.nSecondaryAmmo = 0;
                    this.nSecondaryEngery = 0;
                    this.nSecondaryChargeCostPerFire = 0;
                }
            }
        }
        /// <summary>
        /// Gets the default shot type of the primary weapon
        /// </summary>
        public Type DefaultPrimaryShotType
        {
            get
            {
                return this.tyDefaultPrimaryShotType;
            }
        }
        /// <summary>
        /// Gets the default shot type of the secondary weapon
        /// </summary>
        public Type DefaultSecondaryShotType
        {
            get
            {
                return this.tyDefaultSecondaryShotType;
            }
        }

        public WeaponCategory PrimaryWeaponCategory
        {
            get
            {
                return this.PrimaryWeaponAmmoType;
            }
        }
        public WeaponCategory SecondaryWeaponCategory
        {
            get
            {
                return this.SecondaryWeaponAmmoType;
            }
        }

        public int PrimaryAmmo
        {
            get
            {
                return this.nPrimaryAmmo;
            }
        }
        public int PrimaryEnergy
        {
            get
            {
                return this.nPrimaryEnergy;
            }
        }

        public int SecondaryAmmo
        {
            get
            {
                return this.nSecondaryAmmo;
            }
        }
        public int SecondaryEnergy
        {
            get
            {
                return this.nSecondaryEngery;
            }
        }

        public Dictionary<Type, Texture2D> ShotIcons
        {
            get
            {
                return this.dicShotIcons;
            }
        }

        public bool canPrimaryFire
        {
            get
            {
                return this.bPrimaryReadyToFire;
            }
        }

        #endregion

        /// <summary>
        /// Creates a new weapon manager for the given tank
        /// </summary>
        /// <param name="tank">Tank that owns the weapon manager</param>
        /// <param name="ShotTextures">All Shot textures</param>
        /// <param name="ShotIcons">All Shot icons</param>
        public WeaponManager(Tank tank, Dictionary<Type, Texture2D> ShotTextures, Dictionary<Type, Texture2D> ShotIcons, Dictionary<Type, string> ShotSpriteNames, string animationXMLFile)
        {
            this.tank = tank;

            this.dicShotTextures = ShotTextures;
            this.dicShotIcons = ShotIcons;
            this.dicShotSpriteNames = ShotSpriteNames;

            this.strAnimationXMLFile = animationXMLFile;

            // Initialize audio objects.
            AEngine = new AudioEngine("Content\\Audio\\TWAudio.xgs");
            tankSounds = new SoundBank(AEngine, "Content\\Audio\\Sound Bank.xsb");
            tankWavs = new WaveBank(AEngine, "Content\\Audio\\Wave Bank.xwb");
            primshot = tankSounds.GetCue("Missile");
            anim = tankSounds.GetCue("Impact");


            this.PrimaryShotType = this.tyDefaultPrimaryShotType;
            this.SecondaryShotType = this.tyDefaultSecondaryShotType;
        }

        /// <summary>
        /// Fires the primary weapon
        /// </summary>
        /// <param name="gameTime"></param>
        public void FirePrimary(GameTime gameTime)
        {
            if (this.bPrimaryReadyToFire)
            {
                //Shot shotFired = (Shot)this.tyPrimaryShotType.GetConstructor(new Type[] { typeof(Tank), typeof(Texture2D) }).Invoke(new object[] { this.tank, this.dicShotTextures[this.tyPrimaryShotType] });
                Shot shotFired = (Shot)this.tyPrimaryShotType.GetConstructor(new Type[] { typeof(Tank), typeof(Texture2D), typeof(string), typeof(string) }).Invoke(new object[] { this.tank, this.dicShotTextures[this.tyPrimaryShotType], this.dicShotSpriteNames[this.tyPrimaryShotType], this.strAnimationXMLFile });
                this.lstPrimaryShotsFired.Add(shotFired);

                //Wait before firing again
                this.bPrimaryReadyToFire = false;
                this.dPrimaryLastTimeFired = gameTime.TotalGameTime.TotalMilliseconds;

                //Take off ammo or energy for shot
                if (this.PrimaryWeaponAmmoType == WeaponCategory.Ammo)
                {
                    //Take off one ammo
                    this.nPrimaryAmmo -= 1;
                }
                else if (this.PrimaryWeaponAmmoType == WeaponCategory.Energy)
                {
                    //Take off amount of charge for the weapon
                    this.nPrimaryEnergy -= this.nPrimaryChargeCostPerFire;

                    if (this.nPrimaryEnergy < 0)
                    {
                        this.nPrimaryEnergy = 0;
                    }
                }
                else
                {
                    //Infinite ammo
                }
            }

            //Check if ammo or energy left
            if (this.PrimaryWeaponAmmoType == WeaponCategory.Ammo)
            {
                //If no ammo left
                if (this.nPrimaryAmmo == 0)
                {
                    //Change weapon back to default
                    this.PrimaryShotType = this.tyDefaultPrimaryShotType;
                }
            }
            else if (this.PrimaryWeaponAmmoType == WeaponCategory.Energy)
            {
                //If no energy left
                if (this.nPrimaryEnergy == 0)
                {
                    //Change weapon back to default
                    this.PrimaryShotType = this.tyDefaultPrimaryShotType;
                }
            }
            else
            {
                //Infinite ammo
            }
        }
        /// <summary>
        /// Fires the secondary weapon
        /// </summary>
        /// <param name="gameTime"></param>
        public void FireSecondary(GameTime gameTime)
        {
            if (this.bSecondaryReadyToFire)
            {
                Shot shotFired = (Shot)this.tySecondaryShotType.GetConstructor(new Type[] { typeof(Tank), typeof(Texture2D), typeof(string), typeof(string) }).Invoke(new object[] { this.tank, this.dicShotTextures[this.tySecondaryShotType], this.dicShotSpriteNames[this.tySecondaryShotType], this.strAnimationXMLFile });
                this.lstSecondaryShotsFired.Add(shotFired);

                //Wait before firing again
                this.bSecondaryReadyToFire = false;
                this.dSecondaryLastTimeFired = gameTime.TotalGameTime.TotalMilliseconds;

                //Remove ammo for shot
                if (this.SecondaryWeaponAmmoType == WeaponCategory.Ammo)
                {
                    //Take off one ammo
                    this.nSecondaryAmmo -= 1;
                }
                else if (this.SecondaryWeaponAmmoType == WeaponCategory.Energy)
                {
                    //Take off amount of charge for the weapon
                    this.nSecondaryEngery -= this.nSecondaryChargeCostPerFire;

                    if (this.nSecondaryEngery < 0)
                    {
                        this.nSecondaryEngery = 0;
                    }
                }
                else
                {
                    //Infinite ammo
                }
            }

            //Check if ammo or energy left
            if (this.SecondaryWeaponAmmoType == WeaponCategory.Ammo)
            {
                //If no ammo left
                if (this.nSecondaryAmmo == 0)
                {
                    //Change weapon back to default
                    this.SecondaryShotType = this.tyDefaultSecondaryShotType;
                }
            }
            else if (this.SecondaryWeaponAmmoType == WeaponCategory.Energy)
            {
                //If no energy left
                if (this.nSecondaryEngery == 0)
                {
                    //Change weapon back to default
                    this.SecondaryShotType = this.tyDefaultSecondaryShotType;
                }
            }
            else
            {
                //Infinite ammo
            }
        }

        /// <summary>
        /// Checks to see if primary and secondary weapons are ready to fire.
        /// Updates the position of all primary and secondary shots.
        /// </summary>
        /// <param name="gameTime"></param>
        public void Update(GameTime gameTime)
        {
            this.CheckReadyToFire(gameTime);

            for(int i = 0; i < this.lstPrimaryShotsFired.Count; i++)
            {
                Shot currentShot = this.lstPrimaryShotsFired[i];

                if (!currentShot.bIsExploded)
                {
                    currentShot.UpdatePosition(gameTime);
                }
                else if (currentShot.IsAnimating)
                {
                    primshot.Stop(AudioStopOptions.Immediate);
                    if (anim.IsPlaying)
                    { }
                    else
                    {
                        anim = tankSounds.GetCue("Impact");
                        anim.Play();
                    }

                    currentShot.ContinueAnimation(gameTime);
                }

                if (currentShot.bIsExploded && !currentShot.IsAnimating)
                {
                    this.lstPrimaryShotsFired.RemoveAt(i);
                }
            }

            for (int i = 0; i < this.lstSecondaryShotsFired.Count; i++)
            {
                Shot currentShot = this.lstSecondaryShotsFired[i];

                if (!currentShot.bIsExploded)
                {
                    currentShot.UpdatePosition(gameTime);
                }
                else if (currentShot.IsAnimating)
                {
                    currentShot.ContinueAnimation(gameTime);
                }

                if (currentShot.bIsExploded && !currentShot.IsAnimating)
                {
                    this.lstSecondaryShotsFired.RemoveAt(i);
                }
            }
        }
        /// <summary>
        /// Checks to see if primary and secondary weapons are ready to fire again
        /// </summary>
        /// <param name="gameTime"></param>
        public void CheckReadyToFire(GameTime gameTime)
        {
            if (!this.bPrimaryReadyToFire)
            {
                if ((gameTime.TotalGameTime.TotalMilliseconds - this.dPrimaryLastTimeFired) > this.dPrimaryTimeBetweenFiring)
                {
                    this.bPrimaryReadyToFire = true;
                }
            }

            if (!this.bSecondaryReadyToFire)
            {
                if ((gameTime.TotalGameTime.TotalMilliseconds - this.dSecondaryLastTimeFired) > this.dSecondaryTimeBetweenFiring)
                {
                    this.bSecondaryReadyToFire = true;
                }
            }
        }

        public List<Shot> CheckForShotCollisions(MovableSprite moveableSprite)
        {
            List<Shot> lstShotsHit = new List<Shot>();
            Vector2[] vMoveableSpritePoints = moveableSprite.getPoints();

            for(int i = 0; i < this.lstPrimaryShotsFired.Count; i++)
            {
                Shot currentShot = this.lstPrimaryShotsFired[i];
                if(MovableSprite.shapeCollision(currentShot.getPoints(), vMoveableSpritePoints))
                {
                    lstShotsHit.Add(currentShot);
                }
            }

            for(int i = 0; i < this.lstSecondaryShotsFired.Count; i++)
            {
                Shot currentShot = this.lstSecondaryShotsFired[i];
                if(MovableSprite.shapeCollision(currentShot.getPoints(), vMoveableSpritePoints))
                {
                    lstShotsHit.Add(currentShot);
                }
            }

            return lstShotsHit;
        }

        public void checkFieldCollisions(GameField field, GameTime gameTime)
        {
            for (int i = 0; i < this.lstPrimaryShotsFired.Count; i++)
            {
                Shot currentShot = this.lstPrimaryShotsFired[i];
                if (currentShot.bIsExploded)
                {
                    continue;
                }

                //Convert the bullet's coordinates into field matrix coordinates.
                int fieldX = (int)(currentShot.vPosition.X / field.blockSize);
                int fieldY = (int)(currentShot.vPosition.Y / field.blockSize);

                //Find the bullet radius so we know which field positions to check.
                int bulletRadius = ((currentShot.SpriteWidth / 2) / field.blockSize) + 1;

                //Bool value that determines if the shot has been removed.
                bool shotRemoved = false;

                //Get the current shot's points;
                Vector2[] shotPoints = currentShot.getLine();
  
                //Cycle through the field positions that should be checked.
                for (int x = fieldX - bulletRadius; x <= fieldX + bulletRadius; x++)
                {
                    //Don't continue loop if shot has already been removed.
                    if (shotRemoved) break;

                    for (int y = fieldY - bulletRadius; y <= fieldY + bulletRadius; y++)
                    {
                        //Don't continue loop if shot has already been removed.
                        if (shotRemoved)
                        {
                            break;
                        }

                        //If the bullet collides with the field position.
                        if (MovableSprite.simpleCollision(shotPoints, field.getPoints(x, y)))
                        {
                            //Get cell
                            int cell = field.getCell(x, y);

                            //If there is a block or if the bullet hits a cliff on low ground.
                            if ((cell == 1) || (((currentShot.highGround == false) && (cell == 2))))
                            {
                                currentShot.bIsExploded = true;
                                currentShot.StartAnimation(gameTime, currentShot.ExplodeAnimationLabel);
                                shotRemoved = true;
                            }
                        }
                    }//For: field positions (y)
                }//For: field positions (x)
               
            }//For: primary shots

            for (int i = 0; i < this.lstSecondaryShotsFired.Count; i++)
            {
                Shot currentShot = this.lstSecondaryShotsFired[i];
                if (currentShot.bIsExploded)
                {
                    continue;
                }

                //Convert the bullet's coordinates into field matrix coordinates.
                int fieldX = (int)(currentShot.vPosition.X / field.blockSize + .5);
                int fieldY = (int)(currentShot.vPosition.Y / field.blockSize + .5);

                //Find the bullet radius so we know which field positions to check.
                int bulletRadius = ((currentShot.SpriteWidth / 2) / field.blockSize) + 2;

                //Cycle through the field positions that should be checked.
                for (int x = fieldX - bulletRadius; x <= fieldX + bulletRadius; x++)
                for (int y = fieldY - bulletRadius; y <= fieldY + bulletRadius; y++)
                {
                    //If the bullet collides with the field position.
                    if (MovableSprite.shapeCollision(currentShot.getPoints(), field.getPoints(x, y)))
                    {
                        //Get cell
                        int cell = field.getCell(x, y);

                        //If there is a block or if the bullet hits a cliff on low ground.
                        if ((cell == 1) || (((currentShot.highGround == false) && (cell == 2))))
                        {
                            currentShot.bIsExploded = true;
                            currentShot.StartAnimation(gameTime, currentShot.ExplodeAnimationLabel);
                        }
                    }
                }//For: field positions
            }//For: secondaryShots

        }//end of field collision method

        public void Draw(SpriteBatch spriteBatch, Map map)
        {
            this.DrawPrimary(spriteBatch, map);
            this.DrawSecondary(spriteBatch, map);
            AEngine.Update();
        }

        public void DrawPrimary(SpriteBatch spriteBatch, Map map)
        {
            foreach (Shot currentShot in this.lstPrimaryShotsFired)
            {
                currentShot.Draw(spriteBatch, map);
            }
        }

        public void DrawSecondary(SpriteBatch spriteBatch, Map map)
        {
            foreach (Shot currentShot in this.lstSecondaryShotsFired)
            {
                currentShot.Draw(spriteBatch, map);
            }
        }

        public void AddPrimaryShot(Type ShotType, Vector2 vPosition, float fRotation, bool bExploded)
        {
            Shot shotFired = (Shot)ShotType.GetConstructor(new Type[] { typeof(Tank), typeof(Texture2D), typeof(string), typeof(string) }).Invoke(new object[] { this.tank, this.dicShotTextures[ShotType], this.dicShotSpriteNames[ShotType], this.strAnimationXMLFile });
            shotFired.vPosition = vPosition;
            shotFired.bIsExploded = bExploded;
            this.lstPrimaryShotsFired.Add(shotFired);
        }

        public void AddSecondaryShot(Type ShotType, Vector2 vPosition, float fRotation, bool bExploded)
        {
            Shot shotFired = (Shot)ShotType.GetConstructor(new Type[] { typeof(Tank), typeof(Texture2D), typeof(string), typeof(string) }).Invoke(new object[] { this.tank, this.dicShotTextures[ShotType], this.dicShotSpriteNames[ShotType], this.strAnimationXMLFile });
            shotFired.vPosition = vPosition;
            shotFired.bIsExploded = bExploded;
            this.lstSecondaryShotsFired.Add(shotFired);
        }

        public void UpdatePrimaryShot(int nIndex, Vector2 vPosition, float fRotation, bool bExploded)
        {
            this.lstPrimaryShotsFired[nIndex].vPosition = vPosition;
            this.lstPrimaryShotsFired[nIndex].fRotation = fRotation;
            this.lstPrimaryShotsFired[nIndex].bIsExploded = bExploded;
        }

        public void UpdateSecondaryShot(int nIndex, Vector2 vPosition, float fRotation, bool bExploded)
        {
            this.lstSecondaryShotsFired[nIndex].vPosition = vPosition;
            this.lstSecondaryShotsFired[nIndex].fRotation = fRotation;
            this.lstSecondaryShotsFired[nIndex].bIsExploded = bExploded;
        }

        public void RemovePrimaryShot(int nIndex)
        {
            this.lstPrimaryShotsFired.RemoveAt(nIndex);
        }

        public void RemoveSecondaryShot(int nIndex)
        {
            this.lstSecondaryShotsFired.RemoveAt(nIndex);
        }
    }
}
